﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using IInject.Internal;

namespace IInject.Registration.Scanning
{
	public class DefaultAssemblyScanner : ContainerMember, IAssemblyScanner
	{
		#region Fields

		private readonly List<IScanningRule> _conventions = new List<IScanningRule>();

		#endregion

		#region Constructors

		public DefaultAssemblyScanner(IContainer container) : base(container)
		{
		}

		#endregion

        #region Methods

        private static List<IGrouping<Type, MatchingResult>> GetAggregateMappings(List<MatchingResult> items)
        {
            var mapAll = items.Where(m => m.IsAggregateMapping).ToList();

            if (mapAll.Any())
            {
                items.RemoveAll(m => m.IsAggregateMapping);
            }

            return mapAll.GroupBy(m => m.From).ToList();
        }

        private void MapGroupings(List<IGrouping<Type, MatchingResult>> items)
        {
            for (int i = 0; i < items.Count; i++)
            {
                Container.SetAll(items[i].Key, items[i].Select(m => m.To));
            }
        }

        private void MapItems(List<MatchingResult> items)
        {
            for (int i = 0; i < items.Count; i++)
            {
                Container.Map(a =>
                {
                    a.From(items[i].From);

                    if (items[i].HasToType)
                    {
                        a.To(items[i].To);
                    }
                });
            }
        }

		public virtual void Scan(Assembly assembly)
		{
			if (_conventions.Count == 0)
			{
				_conventions.AddRange(ScanRules.Current.ToArray());
			}

			var types = assembly.GetExportedTypes();
			var context = new ScanningContext(Container, _conventions.ToArray());

			for (int i = 0; i < types.Length; i++)
			{
				context.AddIfMatched(types[i]);
			}

			var items = context.GetPendingItems();

            MapGroupings(GetAggregateMappings(items));
            MapItems(items);
		}

		public void Scan(string assemblyName)
		{
			Scan(Assembly.LoadFrom(assemblyName));
		}

		public void Scan(AssemblyName assemblyName)
		{
			Scan(Assembly.LoadFrom(assemblyName.FullName));
		}

		public void Add(IScanningRule convention)
		{
			_conventions.Add(convention);
		}

		public void Using(params IScanningRule[] conventions)
		{
			_conventions.Clear();
			_conventions.AddRange(conventions);
		}

		public void ScanCallingAssembly()
		{
			Scan(Assembly.GetCallingAssembly());
		}

		public void ScanExecutingAssembly()
		{
			Scan(Assembly.GetExecutingAssembly());
		}

		public void FromEntryAssembly()
		{
			Scan(Assembly.GetEntryAssembly());
		}

		#endregion
	}
}
